package com.heyu.gulimall.product.service.impl;

import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.heyu.common.constant.ProductConstant;
import com.heyu.gulimall.product.dao.AttrAttrgroupRelationDao;
import com.heyu.gulimall.product.dao.AttrGroupDao;
import com.heyu.gulimall.product.dao.CategoryDao;
import com.heyu.gulimall.product.entity.AttrAttrgroupRelationEntity;
import com.heyu.gulimall.product.entity.AttrGroupEntity;
import com.heyu.gulimall.product.entity.CategoryEntity;
import com.heyu.gulimall.product.service.CategoryService;
import com.heyu.gulimall.product.vo.AttrGroupRelationVo;
import com.heyu.gulimall.product.vo.AttrRespVo;
import com.heyu.gulimall.product.vo.AttrVo;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.heyu.common.utils.PageUtils;
import com.heyu.common.utils.Query;

import com.heyu.gulimall.product.dao.AttrDao;
import com.heyu.gulimall.product.entity.AttrEntity;
import com.heyu.gulimall.product.service.AttrService;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;


@Service("attrService")
public class AttrServiceImpl extends ServiceImpl<AttrDao, AttrEntity> implements AttrService {

    @Autowired
    AttrAttrgroupRelationDao relationDao;

    @Autowired
    AttrGroupDao attrGroupDao;

    @Autowired
    CategoryService categoryService;

    @Autowired
    CategoryDao categoryDao;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<AttrEntity> page = this.page(
                new Query<AttrEntity>().getPage(params),
                new QueryWrapper<AttrEntity>()
        );

        return new PageUtils(page);
    }

    //事物注解
    //@Transactional：解释https://www.w3cschool.cn/article/34084832.html
    @Transactional
    @Override
    public void saveAttr(AttrVo attr) {
        //创建AttrEntity实体类对象
        AttrEntity attrEntity = new AttrEntity();
//        attrEntity.setAttrName(attr.getAttrName());

        //BeanUtils：Apache BeanUtils是一种方便我们对JavaBean进行操作的工具类。
        //copyProperties(参数1,参数2）：将参数1中对象数据，拷贝到参数2中
        //  参数1：Object source
        //  参数2：Object target
        // 另外这里来说说attr对象和attrEntity对象的关系。前者是后者的拓展
        BeanUtils.copyProperties(attr, attrEntity);
        //1、保存基本数据
        //default关键字：https://www.jianshu.com/p/1151f3591c60
        //this：this.方法名称。用来访问本类的成员方法。
        //      save是本类父类的接口层中实现的方法（用default关键字）
        //这个方法让attrEntity表中的数据存入数据库表中。
        this.save(attrEntity);

        //2、保存关联关系
        // 判断：
        //     条件1：attr.getAttrType() == ProductConstant.AttrEnum.ATTR_TYPE_BASE.getCode()
        //          attr.getAttrType()：1或0，用于表示是销售属性，还是规格参数（注释写的基本属性）。注意，这个和逻辑删除不一样，不用再配置文件中配置
        //          ProductConstant.AttrEnum.ATTR_TYPE_BASE.getCode()：这是在common模块中自定义的一个枚举类。用与表示常数
        //     条件2：attr.getAttrGroupId()!=null
        //          attr.getAttrGroupId()：得到属性分组的id
        //总：伪代码，若属性类型为规格参数（根据是否等于1来判断）并且要求所属分组id不等于0
        //然而这里为什么要进行判断？
        //  销售属性数据不需要入第三方表？
        //  对，我们来屡屡关系：
        //      所属分组和规格参数有一张第三方表
        //      所属分组和销售属性有一张第三方表（但是这个表没有）
        //      规格参数和销售属性共用一张表，那么第三方表应该也共用，那为什么要使用这个if判断筛选掉后者？
        // 后者不需要进这个第三方表，销售属性是可选属性。
        //  规格参数：
        //  销售属性：黑色、白色、2018、2019
        //想要搞懂这个问题？也就是为什么要通过判断筛选掉销售属性进第三方表。
        //这个数据库，表与表之间的关系，准确来说，就是：spu.sku，销售属性，规格属性的表之间的关系。
        if(attr.getAttrType() == ProductConstant.AttrEnum.ATTR_TYPE_BASE.getCode() && attr.getAttrGroupId()!=null){
            //创建第三方关系表。（多对多关联）
            AttrAttrgroupRelationEntity relationEntity = new AttrAttrgroupRelationEntity();
            //所属分组id入第三方关系表实体类
            relationEntity.setAttrGroupId(attr.getAttrGroupId());
            //属性表中规格参数id入第三方关系表实体类、
            // （
            //      属性表pms_attr中根据value_tape属性分1和0两个部分，
            //          前者：规格参数
            //          后者：销售属性
            //      这里在开始判断时，将销售属性筛选掉了。
            //      其实这里说很多，没什么用。因为根本就不用写多余的代码，很巧妙！只需要知道规格参数和销售属性共用一张表就行了。
            //  ）
            relationEntity.setAttrId(attrEntity.getAttrId());
            //插入实体类数据进数据库表中
            relationDao.insert(relationEntity);
        }
    }

    @Override
    public PageUtils queryBaseAttrPage(Map<String, Object> params, Long catelogId, String type) {
        // 创建条件构造器对象，并附加条件（三目运算符 + 一个条件）
        // 三目运算符：判断是接口5还是接口9
        // 判断请求是查询规范参数，还是销售属性。
        //1.封装条件：
        //      若请求是规格参数，筛选掉销售属性的数据
        //      若请求是销售属性，筛选掉规格参数的数据
        //真牛啊代码!!!
        QueryWrapper<AttrEntity> queryWrapper = new QueryWrapper<AttrEntity>()
                //测重点在后半部分，base是否与type相等，这个方法是互相略大小写。相等返回1，不相等返回0（三目）
                //跳出来后，eq又是一个条件，主要目的：判断请求是查询规范参数，还是销售属性。
                .eq("attr_type","base".equalsIgnoreCase(type)
                ?
                ProductConstant.AttrEnum.ATTR_TYPE_BASE.getCode()
                :
                ProductConstant.AttrEnum.ATTR_TYPE_SALE.getCode()
        );

        //2.若指定了catelogId的值，附加条件
        if(catelogId != 0){
            //条件构造器，附加条件
            queryWrapper.eq("catelog_id",catelogId);
        }

        //3.若输入框有数据，条件构造器附加条件
        //根据key,得到value,并强转为string类型
        String key = (String) params.get("key");
        //如果上一步得到对应的value,就附加条件（如果输入框中有内容，就附加条件）
        if(!StringUtils.isEmpty(key)){
            //attr_id  attr_name
            //老生长谈了
            queryWrapper.and((wrapper)->{
                wrapper.eq("attr_id",key).or().like("attr_name",key);
            });
        }

        //4.1使用分页插件
        //下面调用方法：条件查询，IPage<T> page(IPage<T> page, Wrapper<T> queryWrapper)
        //参数1：翻页对象，个人总结：就是个查询对象（封装了要翻页的页码）
        //（1）Query是一个查询参数类
        //（2）getPage是Query类的一个方法
        //参数2：实体对象封装操作类 QueryWrapper，条件构造器对象
        IPage<AttrEntity> page = this.page(
                new Query<AttrEntity>().getPage(params),
                queryWrapper
        );

        //4.2让IPage类型的对象以PageUtils类型对象返回
        PageUtils pageUtils = new PageUtils(page);

        //5.1提取分页对象的数据——分页记录列表
        List<AttrEntity> records = page.getRecords();
        //5.2JDK8的stream流
        List<AttrRespVo> respVos = records.stream().map((attrEntity) -> {
            //创建vo对象
            AttrRespVo attrRespVo = new AttrRespVo();
            //5.2.1实体类对象数据attrEntity进vo，而每个实体类是什么？是分页记录列表的单个数据，说明每个单个数据是AttrEntity数据
            BeanUtils.copyProperties(attrEntity, attrRespVo);

            //设置分类和分组的名字A
            //若是规格参数
            if("base".equalsIgnoreCase(type)){
                //创建条件构造器，附加条件
                //第三方表dao根据条件构造器查询数据，返回值第三方表entity中
                //这个名字感觉起的不太好！！！
                AttrAttrgroupRelationEntity attrId = relationDao.selectOne(new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_id", attrEntity.getAttrId()));

                //若上一步没查到数据，或数据记录的AttrGroupId属性为null,都跳过这次判断（AttrGroupId属性为null的情况是存在的，意味着attr和所属分组表没关联）
                if (attrId != null && attrId.getAttrGroupId()!=null) {
                    //在attrGroupDao中根据第三方表的AttrGroupId查询出对应的AttrGroupEntity类数据
                    AttrGroupEntity attrGroupEntity = attrGroupDao.selectById(attrId.getAttrGroupId());
                    //5.2.2将AttrGroupEntity类对象中的AttrGroupName名字提取出来，录入vo对象中
                    attrRespVo.setGroupName(attrGroupEntity.getAttrGroupName());
                }

            }

            //在categoryDao中根据attrEntity表的CatelogId查询出对应的CategoryEntity类数据
            CategoryEntity categoryEntity = categoryDao.selectById(attrEntity.getCatelogId());
            if (categoryEntity != null) {
                //5.2.3提取出categoryEntity.getName()，录入vo中
                attrRespVo.setCatelogName(categoryEntity.getName());
            }
            //返回vo对象至vo对象集合中
            return attrRespVo;
        }).collect(Collectors.toList());

        //6.封装，返回。
        //将查询到的指定页码的数据存储到分页工具对象中
        pageUtils.setList(respVos);
        return pageUtils;
    }

    @Override
    public AttrRespVo getAttrInfo(Long attrId) {
        //创建属性实体类vo的对象
        AttrRespVo respVo = new AttrRespVo();
        //根据属性id查询属性信息返回至实体类AttrEntity对象中
        AttrEntity attrEntity = this.getById(attrId);
        //昨天说了，借用javaBean工具类拷贝前者数据到后者（有心的看得出来，这和昨天是反着来的，小——>大
        BeanUtils.copyProperties(attrEntity,respVo);

        //筛选：条件：根据基本属性实体类对象中的attrType属性是否等于1来筛选。等于1进入，等于0跳过（和昨天条件一样）
        if(attrEntity.getAttrType() == ProductConstant.AttrEnum.ATTR_TYPE_BASE.getCode()){
            //1、设置分组信息
            //接收：使用第三张关系表实体类
            AttrAttrgroupRelationEntity attrgroupRelation
                    //relationDao：第三张关系表dao类对象
                    //selectOne()：public abstract T selectOne(@Param("ew") com.baomidou.mybatisplus.core.conditions.Wrapper<T> queryWrapper)
                                //从中可以看出，selectOne函数的参数是一个条件构造器对象
                        //并给条件构造器对象附加一个条件。
                    = relationDao.selectOne(new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_id", attrId));
            //if判断筛选：若上一步第三方关系实体类对象没有数据（即数据库中没有符合条件构造器中条件的数据记录，便跳过）
            if(attrgroupRelation!=null){
                //获取第三方表的AttrGroupId属性数据插入vo实体类对象中
                respVo.setAttrGroupId(attrgroupRelation.getAttrGroupId());
                //根据第三方表的AttrGroupId属性数据，在attrGroupDao对象中查询出数据。并返回至AttrGroupEntity实体类对象中
                AttrGroupEntity attrGroupEntity = attrGroupDao.selectById(attrgroupRelation.getAttrGroupId());
                //若上一步获取的attrGroupEntity不为0，则进入
                if(attrGroupEntity!=null){
                    //获取刚才查询到的attrGroupEntity对象的name，插入vo对象中
                    respVo.setGroupName(attrGroupEntity.getAttrGroupName());
                }
            }
        }

        //2、设置分类信息
        //获取attrEntity对象的中的属性——分类id。
        Long catelogId = attrEntity.getCatelogId();
        //获取一二三级分类id，存入数组中（根据……获取）
        Long[] catelogPath = categoryService.findCatelogPath(catelogId);
        //让一二三级分类id数组数据，存入vo对象中
        respVo.setCatelogPath(catelogPath);

        //下面两步一块说了：
        //1categoryDao根据catelogId查询出数据记录至CategoryEntity对象中
        //2提取刚获得的CategoryEntity类对象数据中的name属性，插入vo对象中。
        CategoryEntity categoryEntity = categoryDao.selectById(catelogId);
        if(categoryEntity!=null){
            respVo.setCatelogName(categoryEntity.getName());
        }

        return respVo;
    }

    //修改接口，一般都需要这个注解
    @Transactional
    @Override
    public void updateAttr(AttrVo attr) {

        //创建实体类对象
        AttrEntity attrEntity = new AttrEntity();

        //工具类复制数据（大——>小）
        BeanUtils.copyProperties(attr, attrEntity);

        //attr表调用更新接口
        this.updateById(attrEntity);

        //还是那个判断，判断是否等于1（是否是规格参数，而非销售属性，前者进，后者筛选掉）
        if (attrEntity.getAttrType() == ProductConstant.AttrEnum.ATTR_TYPE_BASE.getCode()) {
            //1、修改分组关联
            //创建第三方表对象
            AttrAttrgroupRelationEntity relationEntity = new AttrAttrgroupRelationEntity();

            //第三方表对象属性存数据
            //这里可能是个null
            relationEntity.setAttrGroupId(attr.getAttrGroupId());
            //第三方表对象属性存数据
            relationEntity.setAttrId(attr.getAttrId());
            //判断第三方数据库表中有没有数据——等于attr.getAttrId()，有返回1，无返回0
            //attr表中有此条数据便是更新，没有便是插入操作
            //attr表中有此条数据，说明是新增，不是修改。那来看看新增接口是否和这个修改接口共用一个接口
            //并不是一个接口，保存接口就在上面，但是代码类似
            //那么再来想一下是否可能关系表中有找不到对应attr_id属性的值的情况
            //第三方表是 属性分组 和 规格参数 的第三方表
            //若规格参数是个独立的数据，即没有关联属性分组内数据。那么第三方表就找不到数据记录
            //也就是说这个判断是为了：原本规格参数的数据 有没有 指定对应的所属分组数据。
            //前者：指定了数据，修改为其他的指定数据（原本第三方表有数据——>其他数据,所以是update）
            //后者：没有指定数据，修改为指定了数据（原本第三方表没有数据——>有数据,所以是insert
            //     这个后者的有数据，有分为2中情况，倘若第三方表依旧改为没有数据，依旧是这个insert.
            //     只不过AttrGroupId存入空值）
            //另外在说一句，这里没有bug,因为那个所属分组是可选填的！（并不是必填的）
            Integer count = relationDao.selectCount(new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_id", attr.getAttrId()));
            if (count > 0) {
                //第三方表更新数据
                relationDao.update(relationEntity, new UpdateWrapper<AttrAttrgroupRelationEntity>().eq("attr_id", attr.getAttrId()));

            } else {
                //插入了什么数据？
                //1.AttrGroupId
                //2.AttrId
                relationDao.insert(relationEntity);
            }
        }
    }

    /**
     * 根据分组id查找关联的所有基本属性
     * @param attrgroupId
     * @return
     */
    @Override
    public List<AttrEntity> getRelationAttr(Long attrgroupId) {
        // 使用第三方表dao对象调用方法selectList，在第三方表中查询 与所属分组的主键——id属性相等的 属性数据attr_group_id值。
        // 然后存入entities对象中
        List<AttrAttrgroupRelationEntity> entities =
                relationDao.selectList(
                        new QueryWrapper<AttrAttrgroupRelationEntity>()
                        .eq("attr_group_id", attrgroupId )
                );

        //使用stream流遍历集合
        //遍历集合对象entities，提取每个对象的attrid，存入新集合中
        List<Long> attrIds = entities.stream().map((attr) -> {
            return attr.getAttrId();
        }).collect(Collectors.toList());

        //如果能提取不出来，并且长度==0，返回null
        //说明什么都没关联
        if(attrIds == null || attrIds.size() == 0){
            return null;
        }

        //看到这个方法，我只能感慨！mybatisplus真强大啊！！！
        //这个接口我给他起名——批量根据id查询
        //嘿，我给他改进一下怎么样！。。。
//        Collection<AttrEntity> attrEntities = this.listByIds(attrIds);
        List<AttrEntity> attrEntities = this.listByIds(attrIds);
//        return (List<AttrEntity>) attrEntities;
        return attrEntities;
    }

//    @Override
//    //public void deleteRelation(AttrGroupRelationVo[] vos) {
//    public void deleteRelation(List<AttrGroupRelationVo> vos) {
//        //relationDao.delete(new QueryWrapper<>().eq("attr_id",1L).eq("attr_group_id",1L));
//
//        //
////        List<AttrAttrgroupRelationEntity> entities = Arrays.asList(vos).stream().map((item) -> {
//        List<AttrAttrgroupRelationEntity> entities = vos.stream().map((item) -> {
//            AttrAttrgroupRelationEntity relationEntity = new AttrAttrgroupRelationEntity();
//            BeanUtils.copyProperties(item, relationEntity);
//            return relationEntity;
//        }).collect(Collectors.toList());
//        relationDao.deleteBatchRelation(entities);
//    }


    /**
     * 获取当前分组没有关联的所有属性
     * @param params
     * @param attrgroupId
     * @return
     */
    @Override
    public PageUtils getNoRelationAttr(Map<String, Object> params, Long attrgroupId) {
        //1.1根据参数传的所属分组id，查询对应的所属分组实体类对象——这是1比1关系
        AttrGroupEntity attrGroupEntity = attrGroupDao.selectById(attrgroupId);
        //1.2提取出 所属分组实体类对象的分类id属性
        Long catelogId = attrGroupEntity.getCatelogId();

        //屡屡关系：
        //    分类 对 所属分组 = 1 对 n (1对n什么意思？多个所属分组，共用一个分类
        //    所属分组 对 规格参数 = n 对 n（单方面1对n）
        //1.3根据分类id，查询出所有所属分组实体类对象
        List<AttrGroupEntity> group =
                attrGroupDao.selectList(new QueryWrapper<AttrGroupEntity>()
                        .eq("catelog_id", catelogId));
        //1.4遍历所有所属分组实体类对象，提取出每个对象的所属分组id，返回值新集合
        List<Long> collect = group.stream().map(item -> {
            return item.getAttrGroupId();
        }).collect(Collectors.toList());

        //1.5查询 第三方关系表，筛选出表中attr_group_id属性数据 中 属于collect集合的值，然后将其对象封装进一个新集合中
        //到这里，简单来说，就是在第三方表中，groupId封装着单个所属分组对应的所有第三方表对象
        List<AttrAttrgroupRelationEntity> groupId =
                relationDao.selectList(new QueryWrapper<AttrAttrgroupRelationEntity>()
                        .in("attr_group_id", collect));
        //1.6遍历这些集合对象，提取出所有第三方表集合对象的attrId
        List<Long> attrIds = groupId.stream().map(item -> {
            return item.getAttrId();
        }).collect(Collectors.toList());

        //从当前分类的所有属性中移除这些属性；
        //首先条件构造器封装两个必要条件
        QueryWrapper<AttrEntity> wrapper =
                new QueryWrapper<AttrEntity>()
                        .eq("catelog_id", catelogId)
                        .eq("attr_type",ProductConstant.AttrEnum.ATTR_TYPE_BASE.getCode());
        if(attrIds!=null && attrIds.size()>0){
            //再次封装一个条件——查询对象中的 attr_id属性下不属于attrIds的数据
            //上面为了获取这个attrIds不容易啊！！！有很多步骤。这个在总结中说吧
            wrapper.notIn("attr_id", attrIds) ;
        }
        //提取输入框内容
        String key = (String) params.get("key");
        //根据输入框再次封装一个条件
        //至此，最多4个条件
        if(!StringUtils.isEmpty(key)){
            wrapper.and((w)->{
                w.eq("attr_id",key).or().like("attr_name",key);
            });
        }
        //下面调用方法：条件查询，IPage<T> page(IPage<T> page, Wrapper<T> queryWrapper)
        //参数1：翻页对象，个人总结：就是个查询对象（封装了要翻页的页码）
        //（1）Query是一个查询参数类
        //（2）getPage是Query类的一个方法
        //参数2：实体对象封装操作类 QueryWrapper，条件构造器对象
        //这个具体含义，我只能猜出来，并不能真正理解
        IPage<AttrEntity> page = this.page(new Query<AttrEntity>().getPage(params), wrapper);

        PageUtils pageUtils = new PageUtils(page);

        return pageUtils;
    }

    @Override
    public List<Long> selectSearchAttrs(List<Long> attrIds) {

        List<Long> searchAttrIds = this.baseMapper.selectSearchAttrIds(attrIds);

        return searchAttrIds;
    }

}